package org.msfj.gateserver.filters;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import org.msfj.admin.feign.MenuApi;
import org.msfj.admin.feign.UserApi;
import org.msfj.admin.vo.UserVo;
import org.msfj.authority.client.config.JwtConfig;
import org.msfj.common.annotation.Constants;
import org.msfj.common.enums.HttpCode;
import org.msfj.common.exception.TokenErrorException;
import org.msfj.common.model.CommonResult;
import org.msfj.common.util.jwt.IJWTInfo;
import org.msfj.common.util.jwt.JWTHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;

import static org.msfj.common.annotation.Constants.ALL_PERMS_KEY;
import static org.msfj.common.annotation.Constants.USER_PERMS_KEY_PREFIX;

/**
 * @Description: 后台用户token验证过滤器
 * @Author: like
 * @Date: 2017-12-21 11:47
 * @Version: 1.0
 * @Created in idea by autoCode
 */
@Component
public class UserTokenCheckGatewayFilterFactory extends AbstractGatewayFilterFactory {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    JwtConfig jwtConfig;

    @Autowired
    private UserApi userApi;

    @Autowired
    private MenuApi menuApi;

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            final String requestUri = exchange.getRequest().getURI().toString();
            try {
                String userToken = getToken(exchange);
                IJWTInfo user;
                if (StrUtil.isBlank(userToken)) {
                    throw new TokenErrorException("用户token为空");
                } else {
                    user = JWTHelper.getInfoFromToken(userToken, jwtConfig.getPubKeyPath());
                }
                if (user != null) {
                    Object invlideToken = redisTemplate.opsForValue().get(user.getUniqueName());
                    if (ObjectUtil.isNotNull(invlideToken) && userToken.equals(invlideToken.toString())) {
                        throw new TokenErrorException("用户token已过期");
                    } else {
                        ServerHttpRequest request = exchange.getRequest().mutate().header(Constants.CONTEXT_KEY_USER_ID, user.getId()).build();
                        exchange = exchange.mutate().request(request).build();
                        if (user.getBuffer() <= jwtConfig.getMemberBuffer()) {
                            exchange.getResponse().getHeaders().add(Constants.REFRESH_TOKEN, "true");
                        }
                        List<String> userPermissions = getUserPermissions(user.getUniqueName());
                        if (!checkAllow(requestUri, userPermissions, user.getUniqueName())) {
                            ServerHttpResponse response = exchange.getResponse();
                            //设置body
                            CommonResult commonResult = CommonResult.build(HttpCode.USER_TOKEN_FORBIDDEN_CODE.value(), "用户(" + user.getUniqueName() + ")无权限，阻止访问　" + requestUri, null);
                            DataBuffer bodyDataBuffer = response.bufferFactory().wrap(JSONUtil.toJsonStr(commonResult).getBytes());
                            return response.writeWith(Mono.just(bodyDataBuffer));
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                ServerHttpResponse response = exchange.getResponse();
                //设置body
                CommonResult commonResult = CommonResult.build(HttpCode.INTERNAL_SERVER_ERROR.value(), e.getMessage(), null);
                DataBuffer bodyDataBuffer = response.bufferFactory().wrap(JSONUtil.toJsonStr(commonResult).getBytes());
                return response.writeWith(Mono.just(bodyDataBuffer));
            }
            return chain.filter(exchange);
        };
    }

    /**
     * 读取权限
     *
     * @param account
     * @return
     */
    private List<String> getUserPermissions(String account) {
        List<String> permissions = new ArrayList<>();
        List<String> userPermissionsFromCache = this.getUserPermissionsFromCache(account);
        if (userPermissionsFromCache == null) {
            CommonResult<UserVo> userByAccount = userApi.getByAccount(account);
            if (userByAccount.isOK()) {
                UserVo userVo = userByAccount.getData();
                permissions = userVo.getPermissions();
                this.setUserPermissionsToCache(account, permissions);
                logger.debug("cache 中设置用户（" + account + "）的权限为:" + permissions);
            } else {
                logger.error("查询用户(" + account + ")信息失败," + userByAccount.getMsg());
            }
        } else {
            permissions = userPermissionsFromCache;
            logger.debug("cache 中取出用户（" + account + "）的权限为:" + permissions);
        }
        return permissions;
    }

    private String getUserPermissionsCacheKey(String account) {
        if (StrUtil.isNotBlank(account)) {
            return USER_PERMS_KEY_PREFIX + account;
        } else {
            return null;
        }
    }

    private String getAllPermissionsCacheKey() {
        return ALL_PERMS_KEY;
    }

    private void setUserPermissionsToCache(String account, List<String> permissions) {
        this.redisTemplate.opsForValue().set(this.getUserPermissionsCacheKey(account), permissions);
    }

    private List<String> getUserPermissionsFromCache(String account) {
        Object o = this.redisTemplate.opsForValue().get(this.getUserPermissionsCacheKey(account));
        if (o != null) {
            return (List<String>) o;
        }
        return null;
    }

    private List<String> getAllPermissionsFromCache() {
        List<String> data = null;
        Object o = this.redisTemplate.opsForValue().get(this.getAllPermissionsCacheKey());
        if (o != null) {
            return (List<String>) o;
        }
        CommonResult<List<String>> allPermission = this.menuApi.getAllPermission();
        if (allPermission.isOK()) {
            data = allPermission.getData();
            this.redisTemplate.opsForValue().set(getAllPermissionsCacheKey(), data);
        }
        return data;
    }


    /**
     * 获取目标权限资源
     *
     * @param requestUri
     * @param userPermissions
     * @param uniqueName
     * @return
     */
    private boolean checkAllow(final String requestUri, List<String> userPermissions, String uniqueName) {
        boolean hasPermission = false;
        List<String> allPermission = this.getAllPermissionsFromCache();
        List<String> result = CollectionUtil.newArrayList();
        if (CollectionUtil.isNotEmpty(allPermission)) {
            for (String s : allPermission) {
                String uri = requestUri.replaceAll("/", ":");
                if (uri.indexOf(s) >= 0) {
                    result.add(s);
                }
            }
        }
        if (result.size() > 0) {
            if (CollectionUtil.isEmpty(userPermissions)) {
                logger.warn("用户(" + uniqueName + ")无权限，阻止访问　" + requestUri);
                return false;
            } else {
                point:
                for (String userPermission : userPermissions) {
                    for (String permission : result) {
                        if (userPermission.equals(permission)) {
                            hasPermission = true;
                            break point;
                        }
                    }
                }
                if (hasPermission) {
                    //TODO 记录请求日志
                    //setCurrentUserInfoAndLog(ctx, account, pm);
                } else {
                    logger.warn("用户(" + uniqueName + ")无（" + CollectionUtil.join(result, ",") + "）权限，阻止访问　" + requestUri);
                    return false;
                }
            }
        }
        return true;
    }


    /**
     * 返回session中的用户信息
     *
     * @param exchange
     * @return
     */
    private String getToken(ServerWebExchange exchange) throws Exception {

        String authToken = exchange.getRequest().getHeaders().getFirst(jwtConfig.getUserTokenHeader());
        if (StrUtil.isBlank(authToken)) {
            MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
            authToken = queryParams.getFirst("token");
        }
        if (StrUtil.isBlank(authToken)) {
            MultiValueMap<String, HttpCookie> cookies = exchange.getRequest().getCookies();
            HttpCookie cookie = cookies.getFirst(jwtConfig.getUserTokenHeader());
            authToken = cookie != null ? cookie
                    .getValue() : null;
        }
        return authToken;
    }
}
